﻿using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using StackExchange.Redis;

namespace my_webapi_demo.Redis;


/// <summary>
/// Redis帮助函数，不对外公开
/// </summary>
class RedisCacheHelper<T>
{
	public bool TryGetValue(string key, out T value)
	{
		RedisConfig.ValidateFullKey(key);
		
		var items  = _db.HashGetAll(key);
		var record = CacheRecord.Parse(items);

		if (record == null)
		{
			value = default;
			return false;
		}

		value = record.value;

		var expire = record.options.SlidingExpiration;
		if (expire != null)
		{
			_db.KeyExpire(key, expire);
		}

		return true;
	}


	public void SetValue(string key, T value, DistributedCacheEntryOptions options)
	{
		RedisConfig.ValidateFullKey(key);
		
		var record = new CacheRecord(value, options);
		record.GetRedisValue(out var items, out var expire);

		_db.HashSet(key, items);
		if (expire.HasValue)
		{
			_db.KeyExpire(key, expire);
		}
	}

	public void Remove(string key)
	{
		RedisConfig.ValidateFullKey(key);
		
		_db.KeyDelete(key);
	}

	static IDatabase _db => RedisClient.Database;

	//由于需要携带超时信息，映射为hashset
	record CacheRecord(T value, DistributedCacheEntryOptions options)
	{
		public void GetRedisValue(out HashEntry[] items, out TimeSpan? expire)
		{
			items = new HashEntry[]
			{
				new("#sldexp", (int?)options.SlidingExpiration?.TotalSeconds),
				//new("#absexp",(int?)options.AbsoluteExpirationRelativeToNow?.TotalSeconds),
				new("data", JsonConvert.SerializeObject(value)),
			};
			items = items.Where(i => i.Value != RedisValue.Null).ToArray();

			expire = getExpire();
		}


		TimeSpan? getExpire()
		{
			var expires = new TimeSpan?[]
			{
				options.SlidingExpiration,
				options.AbsoluteExpirationRelativeToNow,
				DateTime.Now - options.AbsoluteExpiration,
			};

			return expires.Where(i => i != null)
			              .OrderBy(i => i)
			              .FirstOrDefault();
		}

		public static CacheRecord Parse(HashEntry[] items)
		{
			if (items == null || items.Length == 0)
				return null;

			var data  = items.First(i => i.Name == "data").Value;
			var value = JsonConvert.DeserializeObject<T>(data);

			var options = new DistributedCacheEntryOptions()
			{
				SlidingExpiration = getExpire(items, "#sldexp"),
			};

			return new CacheRecord(value, options);
		}

		static TimeSpan? getExpire(HashEntry[] items, string key)
		{
			var expire = items.FirstOrDefault(i => i.Name == key);
			return expire == default ? null : TimeSpan.FromSeconds((int)expire.Value);
		}
	}
}